﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="X509CertificateHelper.cs" company="AzureContrib (Open source library http://azurecontrib.codeplex.com)">
//   Microsoft Public License (Ms-PL)
// </copyright>
// <summary>
//   Helper class for encrypting and decrypting in Azure using public/private key.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

using System;
using System.Diagnostics.CodeAnalysis;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;

using AzureContrib.WindowsAzure.Properties;

namespace AzureContrib.WindowsAzure
{
	/// <summary>
	/// Helper class for encrypting and decrypting in Azure using public/private key.
	/// </summary>
	public static class X509CertificateHelper
	{
		/// <summary>
		/// Load a certificate from the certificate store on the machine.
		/// </summary>
		/// <param name="storeName">The certificate store to load from.</param>
		/// <param name="storeLocation">The certificate store location.</param>
		/// <param name="thumbprint">The thumbprint for the certificate.</param>
		/// <returns>The <see cref="X509Certificate2"/></returns>
		public static X509Certificate2 LoadCertificate(StoreName storeName, StoreLocation storeLocation, string thumbprint)
		{
			// The following code gets the cert from the keystore
			var store = new X509Store(storeName, storeLocation);

			store.Open(OpenFlags.ReadOnly);

			var certificateCollection = store.Certificates.Find(X509FindType.FindByThumbprint, thumbprint, false);

			var certificateEnumerator = certificateCollection.GetEnumerator();

			X509Certificate2 certificate = null;

			while (certificateEnumerator.MoveNext())
			{
				certificate = certificateEnumerator.Current;
			}

			return certificate;
		}

		/// <summary>
		/// Encrypt some data using a <paramref name="certificate"/>.
		/// </summary>
		/// <param name="plainData">The data to encrypt</param>
		/// <param name="optimalAsymmetricEncryptionPadding">True to perform direct RSA encryption using OAEP padding (only available on a computer running Microsoft Windows XP or later); otherwise, false to use PKCS#1 v1.5 padding.</param>
		/// <param name="certificate">The certificate to encrypt with.</param>
		/// <returns>The encrypted data.</returns>
		[SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", Justification = "AzureContrib.WindowsAzure.Core.Assumes.NotNull handles null checking.")]
		[SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "2", Justification = "AzureContrib.WindowsAzure.Core.Assumes.NotNull handles null checking.")]
		public static byte[] Encrypt(byte[] plainData, bool optimalAsymmetricEncryptionPadding, X509Certificate2 certificate)
		{
			Assumes.NotNull(plainData, "plainData");
			Assumes.NotNull(certificate, "certificate");

			using (var provider = new RSACryptoServiceProvider())
			{
				var publicKey = GetPublicKey(certificate);

				provider.FromXmlString(publicKey);

				// We use the public key to encrypt.
				return provider.Encrypt(plainData, optimalAsymmetricEncryptionPadding);
			}
		}

		/// <summary>
		/// dencrypt some data.
		/// </summary>
		/// <param name="encryptedData">The data to dencrypt</param>
		/// <param name="optimalAsymmetricEncryptionPadding">True to perform direct RSA dencryption using OAEP padding (only available on a computer running Microsoft Windows XP or later); otherwise, false to use PKCS#1 v1.5 padding.</param>
		/// <param name="certificate">The certificate to deencrypt with.</param>
		/// <returns>The dencrypted data.</returns>
		[SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", Justification = "AzureContrib.WindowsAzure.Core.Assumes.NotNull handles null checking.")]
		[SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "2", Justification = "AzureContrib.WindowsAzure.Core.Assumes.NotNull handles null checking.")]
		public static byte[] Decrypt(byte[] encryptedData, bool optimalAsymmetricEncryptionPadding, X509Certificate2 certificate)
		{
			Assumes.NotNull(encryptedData, "encryptedData");
			Assumes.NotNull(certificate, "certificate");

			using (RSACryptoServiceProvider provider = (RSACryptoServiceProvider)certificate.PrivateKey)
			{
				// We use the private key to decrypt.
				return provider.Decrypt(encryptedData, optimalAsymmetricEncryptionPadding);
			}
		}

		/// <summary>
		/// Get the public key from a <paramref name="certificate"/>
		/// </summary>
		/// <param name="certificate">The certificate</param>
		/// <returns>The public key portion of the <paramref name="certificate"/>.</returns>
		[SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", Justification = "AzureContrib.WindowsAzure.Core.Assumes.NotNull handles null checking.")]
		public static string GetPublicKey(X509Certificate2 certificate)
		{
			Assumes.NotNull(certificate, "certificate");
			return certificate.PublicKey.Key.ToXmlString(false);
		}

		/// <summary>
		/// Get the private key from a <paramref name="certificate"/>
		/// </summary>
		/// <param name="certificate">The certificate</param>
		/// <returns>The private key portion of the <paramref name="certificate"/>.</returns>
		[SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", Justification = "AzureContrib.WindowsAzure.Core.Assumes.NotNull handles null checking.")]
		public static string GetPrivateKey(X509Certificate2 certificate)
		{
			Assumes.NotNull(certificate, "certificate");

			if (!certificate.HasPrivateKey)
			{
				throw new ArgumentException(Strings.NoPrivateKey);
			}

			return certificate.PrivateKey.ToXmlString(true);
		}
	}
}